/** \file server/drivers/ezlcd3xx.c
 * LCDd \c exlcd3xx driver for ezLCD-3xx series by EarthLCD.com
 */

/*  This is the LCDproc driver for ezLCD-3xx devices by EarthLCD.com. They
    are not character modules but for the purpose of use by LCDd character
    module behavior can be emulated, along with some graphical enhancements.
    Key input is generated using the touchscreen on devices that have it.

    In order to run this driver you will need additional fonts and images not
    available on the default filesystem at the time of writing. They can be
    obtained from the Downloads section of the project home:
    http://lcdproc-ezlcd3xx.googlecode.com

    Written by Sergey Butylkov on behalf of EarthLCD.com a dba of Earth Computer
    Technologies, Inc.

    Copyright (C) 2013 Sergey Butylkov <zeromemory@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>

#include "ezlcd3xx.h"
#include "lcd.h"
#include "report.h"

/* Vars for the server core */
MODULE_EXPORT char  *api_version = API_VERSION;
MODULE_EXPORT int   stay_in_foreground = 0;
MODULE_EXPORT int   supports_multiple = 0;
MODULE_EXPORT char  *symbol_prefix = "ezlcd3xx_";

#define DEFAULT_SIZE           "8x40"         /**< Default size in characters */
#define DEFAULT_SPEED          115200         /**< Default baud rate */
#define DEFAULT_DEVICE         "/dev/ttyACM0" /**< Default ezLCD device */
#define DEFAULT_FONT           "cm4x20"       /**< Default font filename */
#define DEFAULT_BIGFONT        "cm2x10"       /**< Default big font filename */
#define DEFAULT_HEARTFILLED    "hrf4x20"      /**< Default icon for the filled
                                                 heart */
#define DEFAULT_HEARTOPEN      "hro4x20"      /**< Default icon for the open
                                                 heart */
#define DEFAULT_FOREGROUND     "Aqua"         /**< Default color for text */
#define DEFAULT_BACKGROUND     "Black"        /**< Default color for background
                                               */
#define DEFAULT_BARS           "Lime"         /**< Default color for the bars,
                                               * filled icon, hbars and vbars */
#define DEFAULT_BRIGHTNESS     1000           /**< Default brightness for the
                                               * backlight ON state */
#define DEFAULT_OFFBRIGHTNESS  500            /**< Default brightness for the
                                               * backlight OFF state */
#define DEFAULT_CURSORONTIME   2              /**< Default count for the 
                                               * activation of cursor */
#define DEFAULT_CURSOROFFTIME  4              /**< Default count for the 
                                               * deactivation of the cursor */

/** the screen will have width x height of the following element types */
enum { EZ_CHAR,  /**< simply a character */
       EZ_ICON,  /**< beginning of an icon */
       EZ_HBAR,  /**< horizontal bar element */
       EZ_VBAR,  /**< vertical bar element */
       EZ_NUM,   /**< beginning of a big number */
       EZ_TRAIL, /**< trailing portion of a big number or icon */
       EZ_DIRTY, /**< special type that forces an update when placed in the prev
                    buffer of the private data structure */
       EZ_KEY    /**< represents a touchpad key temporarily shown on the screen*/
};


#define EZ_KEYROWS    4  /**< number of rows of touch keys on the screen */
#define EZ_KEYCOLUMNS 10 /**< number of columns of touch keys on the screen */

#define EZ_UP0    1   /**< keypad 0: up key */
#define EZ_LEFT0  10  /**< keypad 0: left key */
#define EZ_RIGHT0 12  /**< keypad 0: right key */
#define EZ_DOWN0  11  /**< keypad 0: down key */
#define EZ_ESC0   4   /**< keypad 0: esc key */
#define EZ_ENTER0 14  /**< keypad 0: enter key */

#define EZ_UP1    6   /**< keypad 1: up key */
#define EZ_LEFT1  15  /**< keypad 1: left key */
#define EZ_RIGHT1 17  /**< keypad 1: right key */
#define EZ_DOWN1  16  /**< keypad 1: down key */
#define EZ_ESC1   9   /**< keypad 1: esc key */
#define EZ_ENTER1 19  /**< keypad 1: enter key */

#define EZ_UP2    21  /**< keypad 2: up key */
#define EZ_LEFT2  30  /**< keypad 2: left key */
#define EZ_RIGHT2 32  /**< keypad 2: right key */
#define EZ_DOWN2  31  /**< keypad 2: down key */
#define EZ_ESC2   24  /**< keypad 2: esc key */
#define EZ_ENTER2 34  /**< keypad 2: enter key */

#define EZ_UP3    26  /**< keypad 3: up key */
#define EZ_LEFT3  35  /**< keypad 3: left key */
#define EZ_RIGHT3 37  /**< keypad 3: right key */
#define EZ_DOWN3  36  /**< keypad 3: down key */
#define EZ_ESC3   29  /**< keypad 3: esc key */
#define EZ_ENTER3 39  /**< keypad 3: enter key */


#define EZ_INIT_TIMEOUT    10  /**< ezLCD timeout during the initialization */
#define EZ_NORMAL_TIMEOUT  1   /**< timeout for normal operation */

/** element for \c ezlcd3xx driver's frame buffer */
struct ez_element {
        unsigned type : 4;   /**< type of an element in the buffer */
        unsigned data : 12;  /**< data specific to this element
                              * Stores the character for a regular or big 
                              * character. Stores promille in 0-1000 for a vbar
                              * or hbar element. Stores lcdproc's icon index for
                              * an icon element. */
};

/** private data for the \c ezlcd3xx driver */
typedef struct ez_private_data {
        int width;               /**< width of the screen in characters */
        int height;              /**< height of the screen in characters */
        int screen_width_px;     /**< screen width in pixels */
        int screen_height_px;    /**< screen height in pixels */
        int char_width_px;       /**< regular character width in pixels */
        int char_height_px;      /**< regular character height in pixels */
        int big_char_width_px;   /**< height of the big number characters in
                                  * pixels */
        int big_char_height_px;  /**< width of the big number characters in
                                  * pixels */
        int key_width;           /**< width of a touch key in characters */
        int key_height;          /**< height of a touch key in characters */
        int key_width_px;        /**< width of a touch key in pixels */
        int key_height_px;       /**< height of a touch key in pixels */
        int prev_key_id;         /**< Button ID of the last pressed key */

        char *font;              /**< filename for the main font */
        char *big_font;          /**< filename for the font used for big 
                                  * numbers */
        bool disable_big;        /**< true when big number functionality is
                                  * disabled by user or due to the big font
                                  * missing */
        int on_brightness;       /**< brightness in 0-1000 of the ON state of
                                  * the backlight */
        int off_brightness;      /**< brightness in 0-1000 of the OFF state of 
                                  * the backlight */

        int cursor_prev_x;       /**< x-coordinate (in characters) of the last
                                  * cursor request */
        int cursor_prev_y;       /**< y-coordinate (in characters) of the last
                                  * cursor request */
        int cursor_prev_type;    /**< the type of the last cursor request */
        int cursor_blink_count;  /**< counter to make the cursor blink */
        int rxflush_count;       /**< counter for periodically flushing rx data
                                    from ezlcd */
        int prev_light;          /**< the last 'light' value sent to the ezLCD
                                  */
        char *heart_filled;      /**< filename for the filled heart icon */
        char *heart_open;        /**< filename for the open heart icon */
        bool disable_heart;      /**< true when the heartbeat icons are disabled
                                  * by user or due to the icons not found */
        int  foreground;         /**< ezLCD-indexed main color used for text */
        int  background;         /**< ezLCD-indexed color used for background */
        int  bars;               /**< ezLCD-indexed color used for drawing the
                                  * hbars and vbars, filled block icon and the
                                  * touch keys */
        bool up_pressed;         /**< was either of the 'Up' keys pressed? */
        bool left_pressed;       /**< was either of the 'Left' keys pressed? */
        bool right_pressed;      /**< was either of the 'Right' keys pressed? */
        bool down_pressed;       /**< was either of the 'Down' keys pressed? */
        bool esc_pressed;        /**< was either of the 'Escape' keys pressed */
        bool enter_pressed;      /**< was either of the 'Enter' keys pressed */ 
        time_t last_key_press;   /**< time in seconds of the last key touch */
        int active_keypad;       /**< keypad currently active */

        char *comm_buf;          /**< 'communications' buffer for sending to and
                                  * receiving from the ezLCD */
        size_t comm_sz;          /**< size of the 'communications' buffer */
        int fd;                  /**< file descriptor for the com port */

        struct ez_element *frame_buf; /**< Frame buffer with the latest data from
                                       * LCDd */
        struct ez_element *prev_buf;  /**< Buffer with the data last sent to the
                                       * ezLCD */
        struct termios *orig_termios; /**< Original terminal settings */
        struct termios *curr_termios; /**< Current terminal settings */
} PrivateData;


/**
 * Access the frame buffer element at a specified x and y
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \return         pointer to the element
 */
struct ez_element *ez_element_at(Driver *drvthis, int x, int y) {
        PrivateData *p = drvthis->private_data;
        return p->frame_buf + y * p->width + x;
};


/**
 * Access the frame buffer element last sent to ezLCD at a specified x and y
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \return         pointer to the element
 */
struct ez_element *ez_prev_at(Driver *drvthis, int x, int y) {
        PrivateData *p = drvthis->private_data;
        return p->prev_buf + y * p->width + x;
};


/**
 * Set the type and data for the frame buffer element at a specified x and y.
 * Does nothing when the element is occupied by a key.
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \param type     element type
 * \param data     element data
 */
void
ez_element_set(Driver *drvthis, int x, int y, int type, int data)
{
        struct ez_element *e = ez_element_at(drvthis, x, y);
        /* ez_element_set will not change the element when it's occupied by a
         * touch key */
        if (e->type == EZ_KEY)
                return;
        e->type = type;
        e->data = data;
}

/**
 * Activate/deactivate a keypad key with the specified ID
 * \param drvthis   pointer to driver structure
 * \param id        button ID
 * \param activate  true to activate, false to deactivate
 */
void
ez_activate_key(Driver *drvthis, int id, bool activate)
{
        PrivateData *p = drvthis->private_data;
        int x = (id % EZ_KEYCOLUMNS) * p->key_width;
        int y = (id / EZ_KEYCOLUMNS) * p->key_height;
        int a, b;
        for (a = 0; a < p->key_width; ++a)
                for (b = 0; b < p->key_height; ++b) {
                        if (activate)
                                ez_element_set(drvthis, x + a, y + b, EZ_KEY, 0);
                        else { // deactivate
                                struct ez_element *e = 
                                    ez_element_at(drvthis, x + a, y + b);
                                if (e->type == EZ_KEY)
                                        e->type = EZ_DIRTY; // force an update
                        }

                }
}


/**
 * Activate/deactivate all keys of keypad 0
 * \param drvthis   pointer to driver structure
 * \param activate  true to activate, false to deactivate
 */
void
ez_activate_keypad0(Driver *drvthis, bool activate)
{
        ez_activate_key(drvthis, EZ_UP0, activate);
        ez_activate_key(drvthis, EZ_LEFT0, activate);
        ez_activate_key(drvthis, EZ_RIGHT0, activate);
        ez_activate_key(drvthis, EZ_DOWN0, activate);
        ez_activate_key(drvthis, EZ_ESC0, activate);
        ez_activate_key(drvthis, EZ_ENTER0, activate);
}


/**
 * Activate/deactivate all keys of keypad 1
 * \param drvthis   pointer to driver structure
 * \param activate  true to activate, false to deactivate
 */
void
ez_activate_keypad1(Driver *drvthis, bool activate)
{
        ez_activate_key(drvthis, EZ_UP1, activate);
        ez_activate_key(drvthis, EZ_LEFT1, activate);
        ez_activate_key(drvthis, EZ_RIGHT1, activate);
        ez_activate_key(drvthis, EZ_DOWN1, activate);
        ez_activate_key(drvthis, EZ_ESC1, activate);
        ez_activate_key(drvthis, EZ_ENTER1, activate);
}


/**
 * Activate/deactivate all keys of keypad 2
 * \param drvthis   pointer to driver structure
 * \param activate  true to activate, false to deactivate
 */
void
ez_activate_keypad2(Driver *drvthis, bool activate)
{
        ez_activate_key(drvthis, EZ_UP2, activate);
        ez_activate_key(drvthis, EZ_LEFT2, activate);
        ez_activate_key(drvthis, EZ_RIGHT2, activate);
        ez_activate_key(drvthis, EZ_DOWN2, activate);
        ez_activate_key(drvthis, EZ_ESC2, activate);
        ez_activate_key(drvthis, EZ_ENTER2, activate);
}


/**
 * Activate/deactivate all keys of keypad 3
 * \param drvthis   pointer to driver structure
 * \param activate  true to activate, false to deactivate
 */
void
ez_activate_keypad3(Driver *drvthis, bool activate)
{
        ez_activate_key(drvthis, EZ_UP3, activate);
        ez_activate_key(drvthis, EZ_LEFT3, activate);
        ez_activate_key(drvthis, EZ_RIGHT3, activate);
        ez_activate_key(drvthis, EZ_DOWN3, activate);
        ez_activate_key(drvthis, EZ_ESC3, activate);
        ez_activate_key(drvthis, EZ_ENTER3, activate);
}


/**
 * Function to process a key press message from the ezLCD 
 * \param drvthis   pointer to driver structure
 * \param id        button ID
 */
void
ez_keypress(Driver *drvthis, int id)
{
        PrivateData *p = drvthis->private_data;
        int x = (p->prev_key_id % EZ_KEYCOLUMNS) * p->key_width;
        int y = (p->prev_key_id / EZ_KEYCOLUMNS) * p->key_height;
        int a, b;

        for (a = 0; a < p->key_width; ++a)
                for (b = 0; b < p->key_height; ++b) {
                        struct ez_element *prev =
                            ez_prev_at(drvthis, x + a, y + b);
                        prev->type = EZ_DIRTY; // Force an update
                }

        x = (id % EZ_KEYCOLUMNS) * p->key_width;
        y = (id / EZ_KEYCOLUMNS) * p->key_height;
        for (a = 0; a < p->key_width; ++a)
                for (b = 0; b < p->key_height; ++b) {
                        struct ez_element *prev =
                            ez_prev_at(drvthis, x + a, y + b);
                        prev->type = EZ_DIRTY; // Force an update
                }
        p->last_key_press = time(NULL); // Refresh the timer to hide a keypad
}


/**
 * Function to process a key release message from the ezLCD 
 * \param drvthis   pointer to driver structure
 * \param id        button ID
 */
void
ez_keyrelease(Driver *drvthis, int id)
{

        PrivateData *p = drvthis->private_data;
        int x = (id % EZ_KEYCOLUMNS) * p->key_width;
        int y = (id / EZ_KEYCOLUMNS) * p->key_height;

        if (p->active_keypad == 0) {
                switch (id) {
                    case EZ_UP0:
                        p->up_pressed = true; break;
                    case EZ_LEFT0:
                        p->left_pressed = true; break;
                    case EZ_RIGHT0:
                        p->right_pressed = true; break;
                    case EZ_DOWN0:
                        p->down_pressed = true; break;
                    case EZ_ESC0:
                        p->esc_pressed = true; break;
                    case EZ_ENTER0:
                        p->enter_pressed = true; break;
                }
        }
        else if (p->active_keypad == 1) {
                switch (id) {
                    case EZ_UP1:
                        p->up_pressed = true; break;
                    case EZ_LEFT1:
                        p->left_pressed = true; break;
                    case EZ_RIGHT1:
                        p->right_pressed = true; break;
                    case EZ_DOWN1:
                        p->down_pressed = true; break;
                    case EZ_ESC1:
                        p->esc_pressed = true; break;
                    case EZ_ENTER1:
                        p->enter_pressed = true; break;
                }
        }
        else if (p->active_keypad == 2) {
                switch (id) {
                    case EZ_UP2:
                        p->up_pressed = true; break;
                    case EZ_LEFT2:
                        p->left_pressed = true; break;
                    case EZ_RIGHT2:
                        p->right_pressed = true; break;
                    case EZ_DOWN2:
                        p->down_pressed = true; break;
                    case EZ_ESC2:
                        p->esc_pressed = true; break;
                    case EZ_ENTER2:
                        p->enter_pressed = true; break;
                }
        }
        else if (p->active_keypad == 3) {
                switch (id) {
                    case EZ_UP3:
                        p->up_pressed = true; break;
                    case EZ_LEFT3:
                        p->left_pressed = true; break;
                    case EZ_RIGHT3:
                        p->right_pressed = true; break;
                    case EZ_DOWN3:
                        p->down_pressed = true; break;
                    case EZ_ESC3:
                        p->esc_pressed = true; break;
                    case EZ_ENTER3:
                        p->enter_pressed = true; break;
                }
        }


        if (x < p->width / 2) { // Left half of the screen
                if (y < p->height / 2) { // Top half of the screen
                        ez_activate_keypad0(drvthis, true);
                        ez_activate_keypad1(drvthis, false);
                        ez_activate_keypad2(drvthis, false);
                        ez_activate_keypad3(drvthis, false);
                        p->active_keypad = 0;
                }
                else { // Bottom half of the screen
                        ez_activate_keypad0(drvthis, false);
                        ez_activate_keypad1(drvthis, false);
                        ez_activate_keypad2(drvthis, true);
                        ez_activate_keypad3(drvthis, false);
                        p->active_keypad = 2;
                }
        }
        else { // Right half of the screen
                if (y < p->height / 2) { // Top half of the screen
                        ez_activate_keypad0(drvthis, false);
                        ez_activate_keypad1(drvthis, true);
                        ez_activate_keypad2(drvthis, false);
                        ez_activate_keypad3(drvthis, false);
                        p->active_keypad = 1;

                }
                else { // Bottom half of the screen
                        ez_activate_keypad0(drvthis, false);
                        ez_activate_keypad1(drvthis, false);
                        ez_activate_keypad2(drvthis, false);
                        ez_activate_keypad3(drvthis, true);
                        p->active_keypad = 3;
                }
        }
        p->prev_key_id = id;
        p->last_key_press = time(NULL);  // Refresh the timer to hide a keypad
}


/**
 * Read ezLCD output into buffer. Stop on timeout or \<CR\>
 * \param drvthis  Pointer to driver structure.
 * \return         Bytes read, including \<CR\>.
 */
size_t
ez_read(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;
        size_t i = 0;
        char ch = 0;

        while (read(p->fd, &ch, 1) > 0) {
                if (ch == '\r')
                        break;
                p->comm_buf[i] = ch;
                if( ++i == p->comm_sz - 1)
                        break;
        }
        p->comm_buf[i]=0;
        if (ch == '\r')
                ++i;
        if (i > 1)
                report(RPT_DEBUG, "%s: received from ezLCD: %s", drvthis->name,
                       p->comm_buf);
        if (i > 2 && p->comm_buf[0] == 'B' && p->comm_buf[1] == 'P')
        {
                ez_keypress(drvthis, atoi(p->comm_buf + 2));
                return ez_read(drvthis);
        }
        else if (i > 2 && p->comm_buf[0] == 'B' && p->comm_buf[1]=='R')
        {
                ez_keyrelease(drvthis, atoi(p->comm_buf + 2));
                return ez_read(drvthis);
        }
        return i;
}

/**
 * Send contents of the buffer to ezLCD
 * \<CR\> is appended to the contents
 * \param drvthis  Pointer to driver structure.
 * \return         Bytes sent.
 */
size_t
ez_write(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;

        report(RPT_DEBUG, "%s: sending to ezLCD: %s", drvthis->name, p->comm_buf);
        size_t ret = write(p->fd, p->comm_buf, strlen(p->comm_buf));
        ret += write(p->fd, "\r", 1 );
        return ret;
}


/**
 * Send the command in the buffer and read the reply into buffer
 * \param drvthis  Pointer to driver structure.
 * \return         Number of bytes received back.
 */
size_t
ez_command(Driver *drvthis)
{
        ez_write(drvthis);
        return ez_read(drvthis);
}


/**
 * Uses ezLCD's verbose mode to obtrain a numeric value for color from a text \
 value
 * \param drvthis  Pointer to driver structure.
 * \param str      Text value for a color.
 * \return         Numeric value for the color.
 */
int
ez_numeric_color(Driver *drvthis, const char *str)
{
        PrivateData *p = drvthis->private_data;

        sprintf(p->comm_buf, "color %s", str);
        ez_command(drvthis); // Verbose response string now in the buffer
        char *ptr = p->comm_buf;
        while (*ptr && (*ptr < '0' || *ptr > '9'))
                ++ptr;
        return atoi(ptr);
}


/**
 * Draws a picture on an ezLCD
 * \param drvthis  pointer to driver structure.
 * \param x        x-coordinate in pixels on the screen
 * \param y        y-coordinate in pixels on the screen
 * \param pic      picture filename on the ezLCD filesystem
 * \return         0 on success and 1 on failure
 */
int
ez_picture(Driver *drvthis, int x, int y, const char *pic)
{
        PrivateData *p = drvthis->private_data;
        int ret = 0;

        snprintf(p->comm_buf, p->comm_sz, "24 %d %d \"%s\"", x, y, pic);
        // picture
        ez_command(drvthis);
        if (p->comm_buf[0] == '1') {
                report(RPT_WARNING, "%s: picture not found: %s", 
                   drvthis->name, pic);
                ret = 1;
        }
        if (!ret) {                 // Sometimes rogue carriage returns will
                ez_read(drvthis);   // require us to poll twice
                if (p->comm_buf[0] == '1') {
                        report(RPT_WARNING, "%s: picture not found: %s",
                           drvthis->name, pic);
                        ret = 1;
                }
        }
        return ret;
}


/**
 * Compare the two elements to see if redraw is needed.
 * \param curr     current element.
 * \param prev     previous element.
 * \retval true    elements are same and no redraw is needed
 * \retval false   elements are NOT same and redraw is needed
 */
bool
ez_element_same(struct ez_element *curr, struct ez_element *prev)
{
        if (curr->type == prev->type && curr->data == prev->data)
                return true;
        bool curr_empty = (curr->type == EZ_CHAR && curr->data == ' ')
                       || ((curr->type == EZ_VBAR || curr->type == EZ_HBAR)
                           && curr->data == 0);
        bool prev_empty = (prev->type == EZ_CHAR && prev->data == ' ')
                       || ((prev->type == EZ_VBAR || prev->type == EZ_HBAR)
                           && prev->data == 0);
        return curr_empty && prev_empty;
}


/**
 * Draw the largest contiguous block of characters needing redraw starting at x,
 * y, and going in the increasing x direction
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \return         number of elements processed
 */
int
ez_draw_chars(Driver *drvthis, int x, int y)
{
        PrivateData *p = drvthis->private_data;
        //report(RPT_DEBUG, "%s: ez_draw_chars @ %d,%d", drvthis->name, x, y);

        int len = 0, i;
        for (i = x; i < p->width; ++i) {
                struct ez_element *e = ez_element_at(drvthis, i, y);
                struct ez_element *p = ez_prev_at(drvthis, i, y);

                if (e->type == EZ_CHAR && !ez_element_same(e, p)) {
                        ++len;
                        p->type = e->type;
                        p->data = e->data;
                }
                else
                        break;
        }

        /* Draw a rectangle of background color  covering the characters needing
         * a redraw */
        sprintf(p->comm_buf, "15 %d %d", x * p->char_width_px, // xy
            y * p->char_height_px);
        ez_command(drvthis);
        sprintf(p->comm_buf, "6 %d", p->background); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d 1", len * p->char_width_px, // box
            p->char_height_px);
        ez_command(drvthis);

        /* Draw the characters themselves over the rectangle using print */
        sprintf(p->comm_buf, "6 %d", p->foreground); // color
        ez_command(drvthis);
        strcpy(p->comm_buf, "25 \""); // print
        char *ptr = p->comm_buf + strlen(p->comm_buf);
        for (i = 0; i < len; ++i) {
                struct ez_element *e = ez_element_at(drvthis, x + i, y);
                if ( e->data == '\"')
                        *(ptr++) = '\\';
                *(ptr++) = e->data;
        }
        *ptr = 0;
        strcat(p->comm_buf, "\" LT");
        ez_command(drvthis);
        return len;
}


/**
 * Draw the largest contiguous block of hbar elements starting at x, y, and going
 * in the increasing x direction
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \return         number of elements processed
 */
int
ez_draw_hbars(Driver *drvthis, int x, int y)
{
        PrivateData *p = drvthis->private_data;
        //report(RPT_DEBUG, "%s: ez_draw_hbars @ %d,%d", drvthis->name, x, y);

        int len = 0, i;
        int prev_fill = 1000, promille = 0;
        for (i = x; i < p->width; ++i) {
                struct ez_element *e = ez_element_at(drvthis, i, y);
                struct ez_element *p = ez_prev_at(drvthis, i, y);

                if (e->type != EZ_HBAR) {
                        //report(RPT_DEBUG, "%s: hbar stops at x = %d",
                        //    drvthis->name, i);
                        break;
                }
                if (ez_element_same(e, p)) {
                        //report(RPT_DEBUG, "%s: redundant update at x = %d",
                        //    drvthis->name, i);
                        break;
                }
                if (prev_fill < 1000 && e->data > 0) {
                        //report(RPT_DEBUG, "%s: new hbar at x = %d",
                        //    drvthis->name, i);
                        break;
                }
                p->type = e->type;
                p->data = e->data;
                ++len;
                promille += e->data;
                prev_fill = e->data;
        }
        //report(RPT_DEBUG, "%s: promille = %d", drvthis->name, promille);

        /* First draw a background-colored rectangle extending the length
         * of the bar elements we want to draw. */
        sprintf(p->comm_buf, "6 %d", p->background); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "15 %d %d", x * p->char_width_px, // xy
            y * p->char_height_px);
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d 1", len * p->char_width_px, // box
            p->char_height_px);
        ez_command(drvthis);

        /* Now draw the bar. */
        int empty_edge = p->char_height_px / 3;
        sprintf(p->comm_buf, "6 %d", p->bars); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "15 %d %d", x * p->char_width_px, // xy
                y * p->char_height_px + empty_edge);
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d 1", promille * p->char_width_px / 1000,
                p->char_height_px - 2 * empty_edge); // box
        ez_command(drvthis);

        return len;
} 


/**
 * Draw the largest contiguous block of vbar elements starting at x, y, and
 * going in the increasing y direction.
 * \param drvthis  pointer to driver structure.
 * \param x        x-coordinate in characters.
 * \param y        y-coordinate in characters.
 */
void
ez_draw_vbars(Driver *drvthis, int x, int y)
{
        PrivateData *p = drvthis->private_data;
        //report(RPT_DEBUG, "%s: ez_draw_vbars @ %d,%d", drvthis->name, x, y);

        int len = 0, i;
        int prev_fill = 0, promille = 0;
        for (i = y; i < p->height; ++i) {
                struct ez_element *e = ez_element_at(drvthis, x, i);
                struct ez_element *p = ez_prev_at(drvthis, x, i);

                if (e->type != EZ_VBAR) {
                        //report(RPT_DEBUG, "%s: vbar stops at y = %d",
                        //    drvthis->name, i);
                        break;
                }
                if (ez_element_same(e, p)) {
                        //report(RPT_DEBUG, "%s: redundant update at y = %d",
                        //    drvthis->name, i);
                        break;
                }
                if (prev_fill > 0 && e->data < 1000) {
                        //report(RPT_DEBUG, "%s: new vbar at y = %d",
                        //    drvthis->name, i);
                        break;
                }
                p->type = e->type;
                p->data = e->data;
                ++len;
                promille += e->data;
                prev_fill = e->data;
        }
        int empty_edge = p->char_width_px / 4;
        //report(RPT_DEBUG, "%s: promille = %d", drvthis->name, promille);

        /* First draw a background-rectangle to cover the length of the bars
         * needing a (re)draw */
        sprintf(p->comm_buf, "6 %d", p->background); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "15 %d %d", x * p->char_width_px, // xy
            y * p->char_height_px);
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d 1", p->char_width_px, // box
            len * p->char_height_px);
        ez_command(drvthis);

        /* Now draw the bars themselves */
        sprintf(p->comm_buf, "6 %d", p->bars); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "15 %d %d", x * p->char_width_px + empty_edge,
            (y + len) * p->char_height_px - promille * p->char_height_px / 1000);
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d 1", p->char_width_px - 2 * empty_edge,
                promille * p->char_height_px / 1000); // box
        ez_command(drvthis);
}


/**
 * Draws a big number.
 * \param drvthis  pointer to driver structure.
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 */
void ez_draw_num(Driver *drvthis, int x, int y)
{
        PrivateData *p = drvthis->private_data;
        struct ez_element *e = ez_element_at(drvthis, x, y);

        if (!p->disable_big) {
                sprintf(p->comm_buf, "font %s", p->big_font);
                ez_command(drvthis);
                if (p->comm_buf[0] == '1') {   // Failed to set the big font.
                        p->disable_big = true; // Fall back to the alternative
                        return;                // method.
                }
        }
        x = x * p->char_width_px;
        y = y * p->char_height_px;

        /* First draw a background-colored box bahind the big number */
        sprintf(p->comm_buf, "15 %d %d", x, y ); // xy
        ez_command(drvthis);
        sprintf(p->comm_buf, "6 %d", p->background); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d 1", p->char_width_px * 2,
                p->char_height_px * 2); // box
        ez_command(drvthis);

        /* Now draw the big character using a print statement */
        sprintf(p->comm_buf, "15 %d %d", x, y ); // xy
        ez_command(drvthis);
        sprintf(p->comm_buf, "6 %d", p->foreground); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "25 \"%c\" LT", e->data); // print
        ez_command(drvthis);
        sprintf(p->comm_buf, "10 %s", p->font); // font
        ez_command(drvthis);
}


/**
 * Draws a an icon on ezLCD.
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \return         number of elements processed
 */
int
ez_draw_icon(Driver *drvthis, int x, int y)
{
        PrivateData *p = drvthis->private_data;
        struct ez_element *e = ez_element_at(drvthis, x, y);
        int len = 1, i;

        switch (e->data) {
            case ICON_HEART_OPEN:
                if (ez_picture(drvthis, x * p->char_width_px,
                    y * p->char_height_px, p->heart_open))
                        p->disable_heart = true;
                break;
            case ICON_HEART_FILLED:
                if (ez_picture(drvthis, x * p->char_width_px,
                    y * p->char_height_px, p->heart_filled))
                        p->disable_heart = true;
                break;
            case ICON_BLOCK_FILLED:
                len = 0;
                for (i = x; i < p->width; ++i) {
                        struct ez_element *test = ez_element_at(drvthis, i, y);
                        struct ez_element *prev = ez_prev_at(drvthis, i, y);
                        
                        if (test->type == EZ_ICON  
                            && test->data == ICON_BLOCK_FILLED
                            && !ez_element_same(test, prev)) {
                                prev->type = EZ_ICON;
                                prev->data = ICON_BLOCK_FILLED;
                                ++len;
                        }
                        else
                                break;
                }
                x *= p->char_width_px;
                y *= p->char_height_px;
                /* First, a background-colored rectangle to cover the icon 
                 * size */
                sprintf(p->comm_buf, "6 %d", p->background); // color
                ez_command(drvthis);
                sprintf(p->comm_buf, "15 %d %d", x, y); // xy
                ez_command(drvthis);
                sprintf(p->comm_buf, "19 %d %d 1", len * p->char_width_px,
                        p->char_height_px); // box
                ez_command(drvthis);

                /* Now draw three bar-colored lines */
                sprintf(p->comm_buf, "6 %d", p->bars); // color
                ez_command(drvthis);
                for (i = 0; i < 3; ++i) {
                        y += p->char_height_px / 4;
                        sprintf(p->comm_buf, "15 %d %d", x, y); // xy
                        ez_command(drvthis);
                        sprintf(p->comm_buf, "18 %d %d",
                            x + len * p->char_width_px - 1, y); // line
                        ez_command(drvthis);
                }
                break;
        case ICON_ELLIPSIS:
                x *= p->char_width_px;
                y *= p->char_height_px;
                /* First, a background-colored rectangle to cover the icon 
                 * size */
                sprintf(p->comm_buf, "6 %d", p->background); // color
                ez_command(drvthis);
                sprintf(p->comm_buf, "15 %d %d", x, y); // xy
                ez_command(drvthis);
                sprintf(p->comm_buf, "19 %d %d 1", len * p->char_width_px,
                        p->char_height_px); // box
                ez_command(drvthis);

                /* Now draw an ellipse (actually a circle on ezLCD) */
                sprintf(p->comm_buf, "6 %d", p->foreground); // color
                ez_command(drvthis);
                x += p->char_width_px / 2;
                y += p->char_height_px / 2;
                sprintf(p->comm_buf, "15 %d %d", x, y); // xy
                ez_command(drvthis);
                sprintf(p->comm_buf, "20 %d %d 1", p->char_width_px / 2,
                        p->char_width_px / 2); // circle
                ez_command(drvthis);
                break;
           default:
                break;
        }
        return len;
}


/**
 * Draws a touch key
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \return         number of elements processed
 */
int
ez_draw_key(Driver *drvthis, int x, int y)
{
        PrivateData *p = drvthis->private_data;
        if (y % p->key_height || x % p->key_width)
                return 1;
        int id = (y / p->key_height) * 10 + (x / p->key_width);
        char *str;

        switch(id) {
            case EZ_UP0: case EZ_UP1: case EZ_UP2: case EZ_UP3:
                str = "U"; break;
            case EZ_LEFT0: case EZ_LEFT1: case EZ_LEFT2: case EZ_LEFT3:
                str = "L"; break;
            case EZ_RIGHT0: case EZ_RIGHT1: case EZ_RIGHT2: case EZ_RIGHT3:
                str = "R"; break;
            case EZ_DOWN0: case EZ_DOWN1: case EZ_DOWN2: case EZ_DOWN3:
                str = "D"; break;
            case EZ_ESC0: case EZ_ESC1: case EZ_ESC2: case EZ_ESC3:
                str = "X"; break;
            case EZ_ENTER0: case EZ_ENTER1: case EZ_ENTER2: case EZ_ENTER3:
                str = "OK"; break;
            default:
                return 1; break;
        }
        /* First draw a square of 'bars' color */
        x *= p->char_width_px;
        y *= p->char_height_px;
        sprintf(p->comm_buf, "6 %d", p->bars); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "15 %d %d", x, y); // xy
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d 1", p->key_width_px, p->key_height_px);
        ez_command(drvthis);

        /* Draw frame for the button in 'background' color */
        sprintf(p->comm_buf, "6 %d", p->background); // color
        ez_command(drvthis);
        sprintf(p->comm_buf, "15 %d %d", x + 2, y + 2); // xy
        ez_command(drvthis);
        sprintf(p->comm_buf, "19 %d %d", p->key_width_px - 4,
                p->key_height_px - 4); // box
        ez_command(drvthis);

        /* Draw the button text in 'background' color */
        x += p->key_width_px / 2;
        y += p->key_height_px / 2;
        sprintf(p->comm_buf, "15 %d %d", x, y); // xy
        ez_command(drvthis);
        sprintf(p->comm_buf, "25 \"%s\" CC", str); // print
        ez_command(drvthis);
        //sprintf(p->comm_buf, "xy %d %d", x + 2, y + 2);
        //ez_command(drvthis);

        return p->key_width;
}


/**
 * Draw the largest contiguous block of elements at x,y
 * \param drvthis  pointer to driver structure
 * \param x        x-coordinate in characters
 * \param y        y-coordinate in characters
 * \return         number of elements processed
 */
int
ez_draw_at(Driver *drvthis, int x, int y)
{
        struct ez_element *e = ez_element_at(drvthis, x, y);
        struct ez_element *p = ez_prev_at(drvthis, x, y);
        int ret = 1;

        if (ez_element_same(e, p))
                return 1;          // The element does not need a redraw
        switch (e->type) {
        case EZ_CHAR:
                ret = ez_draw_chars(drvthis, x, y);
                break;
        case EZ_HBAR:
                ret = ez_draw_hbars(drvthis, x, y);
                break;
        case EZ_VBAR:
                ez_draw_vbars(drvthis, x, y);
                break;
        case EZ_NUM:
                ez_draw_num(drvthis, x, y);
                break;
        case EZ_ICON:
                ret = ez_draw_icon(drvthis, x, y);
                break;
        case EZ_KEY:
                ret = ez_draw_key(drvthis, x, y);
                break;
        }
        p->type = e->type;
        p->data = e->data;
        return ret > 0 ? ret : 1;
        
}


/**
 * Creates buttons on the ezLCD required for input operation
 * \param drvthis  pointer to driver structure
 */
void
ez_init_keypad(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;

        p->key_width = p->width / EZ_KEYCOLUMNS;
        p->key_height = p->height / EZ_KEYROWS;
        p->key_width_px = p->key_width * p->char_width_px;
        p->key_height_px = p->key_height * p->char_height_px;

        sprintf(p->comm_buf, "fontw 0 %s", p->font);
        ez_command(drvthis);

        /* Set a theme that is all 'bars' color */
        sprintf(p->comm_buf, "theme 0 %d %d %d %d %d %d %d %d %d 0",
            p->bars, p->bars, p->bars, p->bars, p->bars, p->bars, p->bars, 
            p->bars, p->bars);
        ez_command(drvthis);

        /* Set up a blank string */
        strcpy(p->comm_buf, "string 0 \" \"");
        ez_command(drvthis);

        /* Create EZ_KEYROWS x EZ_KEYCOLUMNS buttons with different IDs */
        int x, y;
        for (y = 0; y < EZ_KEYROWS; ++y )
                for (x = 0; x < EZ_KEYCOLUMNS; ++x) {
                        int id = y * EZ_KEYCOLUMNS + x;
                        sprintf(p->comm_buf, "70 %d %d %d %d %d 1 0 0 0 0",
                            id, x * p->key_width_px, y * p->key_height_px, 
                            p->key_width_px - 1, p->key_height_px - 1);
                        // button
                        ez_command(drvthis);
                }

        /* Finally, visually hide the buttons under a box of background color 
         * before LCDd takes over */
        strcpy(p->comm_buf, "xy 0 0");
        ez_command(drvthis);
        sprintf(p->comm_buf, "color %d", p->background);
        ez_command(drvthis);
        sprintf(p->comm_buf, "box %d %d 1", p->screen_width_px,
            p->screen_height_px);
        ez_command(drvthis);
}


/**
 * Initialize the driver.
 * \param drvthis  Pointer to driver structure.
 * \retval 0       Success.
 * \retval <0      Error.
 */

MODULE_EXPORT int
ezlcd3xx_init(Driver *drvthis)
{
        PrivateData *p;
        const char *device;
        const char *size;
        const char *foreground_str, *background_str, *bars_str;
        int speed;
        speed_t baud;
        bool verbose = false;      /**< true when verbose reply is received from
                                    * ezLCD during the ping test */
        bool echo = false;         /**< true when the ping commands are echoed */
        bool response_ok = false;  /**< true if there was an ok response from
                                    * ping */
        int i, n;

        /* Allocate and store private data */
        p = (PrivateData *) calloc(1, sizeof(PrivateData));
        if (p == NULL)
                return -1;
        if (drvthis->store_private_ptr(drvthis, p))
                return -1;

        /* Private data and temp variable intialization */

        /* Initialize values */
        p->fd = -1;
        p->cursor_prev_x = 0;
        p->cursor_prev_y = 0;
        p->cursor_prev_type = CURSOR_OFF;
        p->cursor_blink_count = 0;
        p->rxflush_count = 0;
        p->disable_big = false;
        p->disable_heart = false;
        p->prev_key_id = 0;
        p->prev_light = -1;
        p->up_pressed = false;
        p->left_pressed = false;
        p->right_pressed = false;
        p->down_pressed = false;
        p->esc_pressed = false;
        p->enter_pressed = false;
        p->last_key_press = 0;

        /* Configure width and height */
        size = drvthis->config_get_string(drvthis->name, "Size", 0,
                            DEFAULT_SIZE);
        sscanf(size, "%dx%d", &(p->height), &(p->width));
        if (p->width <= 0 || p->height <= 0) {
                report(RPT_CRIT, "%s: invalid screen configuration",
                    drvthis->name);
                return -1;
        }
                
        /* Configure the font for regular text */
        p->font = malloc(16);
        if (p->font == NULL) {
                report(RPT_CRIT, "%s: unable to allocate font name buffer",
                    drvthis->name);
                return -1;
        }
        /* Guess font filename based on width and height */
        snprintf(p->font, 16, "cm%dx%d", p->height, p->width);
        if (drvthis->config_has_key(drvthis->name, "Font")) {
                /* Override automatically determined font */
                const char *temp = drvthis->config_get_string(drvthis->name,
                                                "Font", 0, DEFAULT_FONT);
                strncpy(p->font, temp, 16);
        }

        /* Configure the font for big numbers */
        p->big_font = malloc(16);
        if (p->big_font == NULL) {
                report(RPT_CRIT, "%s: unable to allocate font name buffer",
                           drvthis->name);
                return -1;
        }
        /* Guess the filename based on width and height */
        snprintf(p->big_font, 16, "cm%dx%d", p->height / 2, p->width / 2);
        if (drvthis->config_has_key(drvthis->name, "BigFont")) { 
                /* Override automatically determined big font */
                const char *temp = drvthis->config_get_string(drvthis->name,
                                                "BigFont", 0, DEFAULT_BIGFONT);
                strncpy(p->big_font, temp, 16);
                if (!strcasecmp(p->big_font, "off") || 
                    !strcasecmp(p->big_font, "false") ||
                    !strcasecmp(p->big_font, "disable"))
                        p->disable_big = true; // Disabled via LCDd.conf
        }
                      

        /* Configure the filled heart icon */
        p->heart_filled = malloc(16);
        if (p->heart_filled == NULL) {
                report(RPT_CRIT, "%s: unable to allocate heart name buffer",
                   drvthis->name);
                return -1;
        }
        /* Guess the filename based on width and height */
        snprintf(p->heart_filled, 16, "hrf%dx%d.gif", p->height, p->width);
        if (drvthis->config_has_key(drvthis->name, "HeartFilled")) {
                /* Override automatically determined heart filename */
                const char *temp = drvthis->config_get_string(drvthis->name, 
                                       "HeartFilled", 0, DEFAULT_HEARTFILLED);
                strncpy(p->heart_filled, temp, 16);
                if (!strcasecmp(p->heart_filled, "off") ||
                    !strcasecmp(p->heart_filled, "false") ||
                    !strcasecmp(p->heart_filled, "disable"))
                        p->disable_heart = true; // Disabled via LCDd.conf
        }

        /* Configure the open heart icon */
        p->heart_open = malloc(16);
        if (p->heart_open == NULL) {
                report(RPT_CRIT, "%s: unable to allocate heart name buffer",
                    drvthis->name);
                return -1;
        }
        /* Guess the filename based on width and height */
        snprintf(p->heart_open, 16, "hro%dx%d.gif", p->height, p->width);
        if (drvthis->config_has_key(drvthis->name, "HeartOpen")) { 
                /* Override automatically determined heart filename */
                const char *temp = drvthis->config_get_string(drvthis->name,
                                       "HeartOpen", 0, DEFAULT_HEARTOPEN);
                strncpy(p->heart_open, temp, 16);
                if (!strcasecmp(p->heart_open, "off") ||
                    !strcasecmp(p->heart_open, "false") ||
                    !strcasecmp(p->heart_open, "disable"))
                        p->disable_heart = true; // Disabled via LCDd.conf
        }

        /* Load a verbose or numeric name for the foreground color */
        foreground_str = drvthis->config_get_string(drvthis->name, "Foreground",
                                      0, DEFAULT_FOREGROUND);
        /* Load a verbose or numeric name for the background color */
        background_str = drvthis->config_get_string(drvthis->name, "Background",
                                      0, DEFAULT_BACKGROUND);
        /* Load a verbose or numeric name for the 'bars' color */
        bars_str = drvthis->config_get_string(drvthis->name, "Bars", 0, 
                                DEFAULT_BARS);

        /* Configure device */
        device = drvthis->config_get_string(drvthis->name, "Device", 0,
                              DEFAULT_DEVICE);

        /* Configure ON brightness */
        p->on_brightness = drvthis->config_get_int(drvthis->name, "Brightness",
                                        0, DEFAULT_BRIGHTNESS);

        /* Configure OFF brightness */
        p->off_brightness = drvthis->config_get_int(drvthis->name, 
                                "OffBrightness", 0, DEFAULT_OFFBRIGHTNESS);

        /* Configure the baud rate */
        speed = drvthis->config_get_int(drvthis->name, "Speed", 0,
                             DEFAULT_SPEED);
        switch (speed) {
            case 0: baud = B0; break;
            case 50: baud = B50; break;
            case 75: baud = B75; break;
            case 110: baud = B110; break;
            case 134: baud = B134; break;
            case 150: baud = B150; break;
            case 200: baud = B200; break;
            case 300: baud = B300; break;
            case 600: baud = B600; break;
            case 1200: baud = B1200; break;
            case 1800: baud = B1800; break;
            case 2400: baud = B2400; break;
            case 4800: baud = B4800; break;
            case 9600: baud = B9600; break;
            case 19200: baud = B19200; break;
            case 57600: baud = B57600; break;
            case 115200: baud = B115200; break;
            case 230400: baud = B230400; break;
            default: baud = B9600; break;
        };
               
        /* Allocate the frame buffer */
        size_t sz = sizeof(struct ez_element) * p->width * p->height;
        p->frame_buf = (struct ez_element *) malloc(sz);
        if(p->frame_buf == NULL) {
                report(RPT_CRIT, "%s: unable to allocate the frame buffer",
                    drvthis->name);
                return -1;
        }
        /* Allocate the buffer representing elements last sent to the ezLCD */
        p->prev_buf = (struct ez_element *) malloc(sz);
        if(p->prev_buf == NULL) {
                report(RPT_CRIT, "%s: unable to allocate the frame buffer",
                    drvthis->name);
                return -1;
        }
        /* Set up the frame buffer and previous frame buffer */
        int x, y;
        for (y = 0; y < p->height; ++y)
                for (x = 0; x < p->width; ++x) {
                        struct ez_element *e = ez_element_at(drvthis, x, y);
                        struct ez_element *p = ez_prev_at(drvthis, x, y);
                        e->type = p->type = EZ_CHAR;
                        e->data = p->data = ' ';
                }

        /* Allocate the buffer for sending to and receiving from the ezLCD */
        p->comm_sz = p->width * 2; // Maximum size of the buffer
        p->comm_buf = malloc(p->comm_sz);
        if (p->comm_buf == NULL) {
                report(RPT_CRIT, "%s: unable to allocate the rx/tx buffer",
                    drvthis->name);
                return -1;
        }
        /* Backup the original terminal settings so they can be restored */
        p->orig_termios = malloc(sizeof(struct termios));
        if (p->orig_termios == NULL) {
                report(RPT_CRIT, "%s: unable to allocate terminal data", 
                    drvthis->name);
                return -1;
        }
        /* Maintain current terminal settings for a quick adjustment */
        p->curr_termios = malloc(sizeof(struct termios));
        if (p->curr_termios == NULL) {
                report(RPT_CRIT, "%s: unable to allocate terminal data", 
                    drvthis->name);
                return -1;
        }

        /* Setup io */
        p->fd = open(device, O_RDWR | O_NOCTTY /*| O_NDELAY*/ );
        if (p->fd == -1 ) {
                report(RPT_CRIT, "%s: open(%s) failed (%s)", drvthis->name,
                    device, strerror(errno));
                return -1;
        }
        tcgetattr(p->fd, p->orig_termios);
        cfmakeraw(p->curr_termios);
        cfsetispeed(p->curr_termios, baud);
        cfsetospeed(p->curr_termios, baud);
        /* Configure initial timeout for rx from ezLCD in 1/10th of a second */
        p->curr_termios->c_cc[VTIME] = EZ_INIT_TIMEOUT;
        p->curr_termios->c_cc[VMIN] = 0;
        tcsetattr(p->fd, TCSANOW, p->curr_termios);

        /* Read any response/initialization data built up by the ezLCD */
        while (ez_read(drvthis) > 0);
        /* Begin the ping test */
        for (i=0; i < 100; i++) {
                strcpy(p->comm_buf, "ping");
                ez_write(drvthis);
                n = ez_read(drvthis); // Will wait EZ_INIT_TIMEOUT x 1/10s
                if (n > 0) { 
                        /* Something was read */
                        if (!strcmp(p->comm_buf, "ping")) {
                                /* Echo response detected */
                                echo = true;
                                /* Now read the actual response */
                                n = ez_read(drvthis); 
                                report(RPT_INFO, "%s: echo response detected "
                                    "from ezLCD", drvthis->name);
                        }
                        if (!strcmp(p->comm_buf, "Pong")) {
                                /* Verbose response detected */
                                verbose = true;
                                /* Successful response */
                                response_ok = true;
                                report(RPT_INFO, "%s: verbose response "
                                    "detected from ezLCD", drvthis->name);
                                break;
                        }
                        else if (!strcmp(p->comm_buf, "0")) {
                                /* Regular, non-verbose response. Success */
                                response_ok = true;
                                report(RPT_INFO, "%s: non-verbose response "
                                    "detected from ezLCD", drvthis->name);
                                break;
                        }
                }
        }
        if (!response_ok) {
                report(RPT_CRIT, "%s: failed to establish communication with the "
                    "ezLCD after 100 attempts", drvthis->name);
                return -1;
        }
        if (echo) {
                /* Disable echo */
                strcpy(p->comm_buf, "echo 0");
                ez_command(drvthis);
        }
        if (!verbose) {
                /* First enable verbose so we can extract numeric values for
                 * colors */
                strcpy(p->comm_buf, "verbose 1");
                ez_command(drvthis);
        }
        /* Read any additional response */
        while (ez_read(drvthis) > 0);
        /* Use the verbose mode to convert the string values to numbers for the
         * background, foreground, and bars colors */
        p->foreground = ez_numeric_color(drvthis, foreground_str);
        p->background = ez_numeric_color(drvthis, background_str);
        p->bars = ez_numeric_color(drvthis, bars_str);
        /* Now disable verbose to minimize data coming from the ezLCD */
        strcpy(p->comm_buf, "verbose 0");
        ez_command(drvthis);
        /* Read any additional response */
        while (ez_read(drvthis) > 0);

        /* Detect the screen width */
        strcpy(p->comm_buf, "xmax");
        ez_command(drvthis);
        p->screen_width_px = atoi(p->comm_buf) + 1;
        report(RPT_INFO, "%s: detected screen width = %d", drvthis->name, 
            p->screen_width_px);
        /* Detect the screen height */
        strcpy(p->comm_buf, "ymax");
        ez_command(drvthis);
        p->screen_height_px = atoi(p->comm_buf) + 1;
        report(RPT_INFO, "%s: detected screen height = %d", drvthis->name,
            p->screen_height_px);
        /* Sanity check for screen width and height */
        if (p->screen_width_px == 0 || p->screen_height_px == 0) {
                report(RPT_CRIT, "%s: Invalid screen width and/or height: %dx%d",
                    drvthis->name, p->screen_width_px, p->screen_height_px);
                return -1;
        }
                
        /* Compute character width and height in pixels */
        p->char_width_px = p->screen_width_px / p->width;
        p->char_height_px = p->screen_height_px / p->height;

        /* Set the font and ensure ezLCD finds the filename */
        snprintf(p->comm_buf, p->comm_sz, "font %s", p->font);
        ez_command(drvthis);
        if (p->comm_buf[0] == '1') {
                /* Error setting font */
                report(RPT_CRIT, "%s: Failed to set LCD font: %s", drvthis->name,
                    p->font);
                return -1;
        }
        /* Clear screen to get rid of anything drawn on ezLCD before us */
        snprintf(p->comm_buf, p->comm_sz, "cls %d", p->background);
        ez_command(drvthis);
        /* Initialize the keypad buttons */
        ez_init_keypad(drvthis);

        /* Configure the terminal settings to a faster timeout for regular
         * operation */
        p->curr_termios->c_cc[VTIME] = EZ_NORMAL_TIMEOUT;
        tcsetattr(p->fd, TCSANOW, p->curr_termios);

        return 0;
}


/**
 * Close the driver (do necessary clean-up).
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
ezlcd3xx_close(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;

        if (p != NULL) {
                if (p->fd != -1) {
                        if (p->orig_termios != NULL)
                                /* Restore the original terminal settings */
                                tcsetattr(p->fd, TCSANOW, p->orig_termios);
                        close(p->fd);
                }                        
                if (p->frame_buf != NULL)
                        free(p->frame_buf);
                if (p->comm_buf != NULL)
                        free(p->comm_buf);
                if (p->orig_termios != NULL)
                        free(p->orig_termios);
                if (p->curr_termios != NULL)
                        free(p->curr_termios);
                if (p->font != NULL)
                        free(p->font);
                if (p->big_font != NULL)
                        free(p->big_font);
                if (p->heart_filled != NULL)
                        free(p->heart_filled);
                if (p->heart_open != NULL)
                        free(p->heart_open);
                free(p);
        }
        drvthis->store_private_ptr(drvthis, NULL);
}


/**
 * Return the display width in characters.
 * \param drvthis  Pointer to driver structure.
 * \return         Number of characters the display is wide.
 */
MODULE_EXPORT int
ezlcd3xx_width(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;
        return p->width;
}


/**
 * Return the display height in characters.
 * \param drvthis  Pointer to driver structure.
 * \return         Number of characters the display is high.
 */
MODULE_EXPORT int
ezlcd3xx_height(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;
        return p->height;
}


/**
 * Clear the screen.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
ezlcd3xx_clear(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;

        /* Set the buffer to a bunch of space characters */
        int x, y;
        for (y = 0; y < p->height; ++y)
                for (x = 0; x < p->width; ++x)
                        ez_element_set(drvthis, x, y, EZ_CHAR, ' ');
}


/**
 * Flush data on screen to the display.
 * \param drvthis  Pointer to driver structure.
 */
MODULE_EXPORT void
ezlcd3xx_flush(Driver *drvthis)
{
        PrivateData *p = drvthis->private_data;
        int x, y;

        /* If there is an active keypad and 4 seconds have passed since it was
         * last touched, hide it now */
        if (p->active_keypad != -1 && time(NULL) - p->last_key_press > 4) {
                ez_activate_keypad0(drvthis, false);
                ez_activate_keypad1(drvthis, false);
                ez_activate_keypad2(drvthis, false);
                ez_activate_keypad3(drvthis, false);
                p->active_keypad = -1;
        }

        /* Draw the elements. ez_draw_at will determine wether each of the
         * elements needs an update */
        for (y = 0; y < p->height; ++y)
                for (x = 0; x < p->width; )
                        x += ez_draw_at(drvthis, x, y);

        /* Every now and then get rid of unaccounted data built up from ezLCD */
        if (++(p->rxflush_count) == 10) {
                while (ez_read(drvthis) > 0);
                p->rxflush_count = 0;
        }
}


/**
 * Print a string on the screen at position (x,y).
 * The upper-left corner is (1,1), the lower-right corner is (p->width, \
p->height).
* \param drvthis  Pointer to driver structure.
* \param x        Horizontal character position (column).
* \param y        Vertical character position (row).
* \param string   String that gets written.
*/
MODULE_EXPORT void
ezlcd3xx_string(Driver *drvthis, int x, int y, const char string[])
{
        PrivateData *p = drvthis->private_data;
        //report(RPT_DEBUG, "%s: string: \"%s\" @ xy: %d,%d", drvthis->name,
        //    string, x-1, y-1);

        if (x < 1 || x > p->width)
                return;
        if (y < 1 || y > p->height)
                return;

        const char *ptr = string; // Pointer to iterate through the string
        while (*ptr != 0 && x <= p->width ) {
               ezlcd3xx_chr(drvthis, x, y, *ptr);
                ++ptr;
                ++x;
        }
}        


/**
 * Print a character on the screen at position (x,y).
 * The upper-left corner is (1,1), the lower-right corner is (p->width, \
p->height).
* \param drvthis  Pointer to driver structure.
* \param x        Horizontal character position (column).
* \param y        Vertical character position (row).
* \param c        Character that gets written.
*/
MODULE_EXPORT void
ezlcd3xx_chr(Driver *drvthis, int x, int y, char c)
{
        PrivateData *p = drvthis->private_data;
        //report(RPT_DEBUG, "%s: char: %c @ xy: %d,%d", drvthis->name, c, x, y);

        --x; --y;
        if (x < 0 || x > p->width)
                return;
        if (y < 0 || y > p->height)
                return;

        ez_element_set(drvthis, x, y, EZ_CHAR, c);
}


/**
 * Provide some information about this driver.
 * \param drvthis  Pointer to driver structure.
 * \return         Constant string with information.
 */
MODULE_EXPORT const char *
ezlcd3xx_info(Driver *drvthis)
{
        static char *info_string = "ezlcd3xx driver";
        return info_string;
}


/**
 * Retrieve brightness.
 * \param drvthis  Pointer to driver structure.
 * \param state    Brightness state (on/off) for which we want the value
 * \return Stored brightness in promille.
*/
MODULE_EXPORT int
ezlcd3xx_get_brightness(Driver *drvthis, int state)
{
        PrivateData *p = drvthis->private_data;

        return state == BACKLIGHT_ON 
               ? p->on_brightness
               : p->off_brightness;
}


/**
 * Set on/off brightness.
 * \param drvthis  Pointer to driver structure.
 * \param state    Brightness state (on/off) for which we want to store the value.
 * \param promille New brightness in promille.
 */
MODULE_EXPORT void
ezlcd3xx_set_brightness(Driver *drvthis, int state, int promille)
{
        PrivateData *p = drvthis->private_data;

        if (promille < 0 || promille > 1000)
                return;

        if (state == BACKLIGHT_ON)
                p->on_brightness = promille;
        else
                p->off_brightness = promille;
}


/**
 * Turn the LCD backlight on or off.
 * \param drvthis  Pointer to driver structure.
 * \param on       New backlight status.
 */
MODULE_EXPORT void
ezlcd3xx_backlight(Driver *drvthis, int on)
{
        PrivateData *p = drvthis->private_data;

        int value = (on == BACKLIGHT_ON)
                  ? p->on_brightness
                  : p->off_brightness;
        value /= 10;
        if (value != p->prev_light) {
                sprintf(p->comm_buf, "5 %d", value); // light
                ez_command(drvthis);
        }
}


/**
 * Write a big number to the screen.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column).
 * \param num      Character to write (0 - 10 with 10 representing ':')
 */

MODULE_EXPORT void
ezlcd3xx_num(Driver *drvthis, int x, int num)
{
        PrivateData *p = drvthis->private_data;
        --x;

        char ch;
        if (num == 10) {
                ch = ':';
                --x; // colon (:) looks nicer when shifted left
        }
        else
                ch = '0' + num;
        int y = p->height / 2 - 1; 
        
        ez_element_set(drvthis, x, y, EZ_NUM, ch);
        /* Trail elements ensure proper redraw when other elements will be drawn
         * over them later */
        ez_element_set(drvthis, x + 1, y, EZ_TRAIL, ch);
        ez_element_set(drvthis, x, y + 1, EZ_TRAIL, ch);
        ez_element_set(drvthis, x + 1, y + 1, EZ_TRAIL, ch);
}


/**
 * Set cursor position and state.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal cursor position (column).
 * \param y        Vertical cursor position (row).
 * \param type     New cursor state.
 */
MODULE_EXPORT void
ezlcd3xx_cursor(Driver *drvthis, int x, int y, int type)
{
        PrivateData *p = (PrivateData *) drvthis->private_data;
        --x; --y;

        if (x != p->cursor_prev_x || y != p->cursor_prev_y) { 
                /* Force an update and draw at the previous cursor position */
                ez_prev_at(drvthis, p->cursor_prev_x, p->cursor_prev_y)->type
                    = EZ_DIRTY;
                ez_draw_at(drvthis, p->cursor_prev_x, p->cursor_prev_y);
        }

        /* Previous cursor position and type are saved to ensure proper redraw
         * at that position */
        p->cursor_prev_x = x;
        p->cursor_prev_y = y;
        p->cursor_prev_type = type;
        int x_px = x * p->char_width_px;
        int y_px = y * p->char_height_px;
        struct ez_element *prev = ez_prev_at(drvthis, x, y);

        /* Now decide how to draw/hide the cursor based on parameters */
        switch (type) {
            case CURSOR_OFF:
                /* No cursor - restore the element */
                if (p->cursor_prev_type == CURSOR_OFF)
                        return; // Redundant update
                /* Force an update and draw */
                prev->type = EZ_DIRTY;
                ez_draw_at(drvthis, x, y);                
                break;
            case CURSOR_UNDER:
                /* Blinking underline cursor */
                if (p->cursor_blink_count == DEFAULT_CURSORONTIME) {
                        /* Blink counter says we draw the cursor now */
                        int boxheight = p->char_height_px / 10;
                        y_px += p->char_height_px - boxheight;
                        sprintf(p->comm_buf, "15 %d %d", x_px, y_px); // xy
                        ez_command(drvthis);
                        sprintf(p->comm_buf, "6 %d", p->foreground); // color
                        ez_command(drvthis);
                        sprintf(p->comm_buf, "19 %d %d 1", p->char_width_px - 1,
                                boxheight); // box
                        ez_command(drvthis);
                }
                else if(p->cursor_blink_count == DEFAULT_CURSOROFFTIME) {
                        /* Blink counter says we need to hide the cursor by
                         * forcing an update and drawing */
                        prev->type = EZ_DIRTY;
                        ez_draw_at(drvthis, x, y);                
                }
                break;
            case CURSOR_BLOCK:
                /* Blinking inverted block */
                if (p->cursor_blink_count == DEFAULT_CURSORONTIME) {
                        /* Blink counter says we draw the cursor now */
                        /* First, avoid a possible bug due to an optimized draw
                           at x,y drawing elements at increasing x */
                        if (x < p->width - 1)
                            ez_draw_at(drvthis, x + 1, y);
                        /* Force an update and draw characters in an inverted
                         * color */
                        prev->type = EZ_DIRTY;
                        int temp = p->background;
                        p->background = p->foreground;
                        p->foreground = temp;
                        ez_draw_at(drvthis, x, y);
                        p->foreground = p->background;
                        p->background = temp;
                }
                else if(p->cursor_blink_count == DEFAULT_CURSOROFFTIME) {
                        /* Blink counter says we need to hide the cursor
                           by forcing an update and drawing */
                        prev->type = EZ_DIRTY;
                        ez_draw_at(drvthis, x, y);
                }
                break;
            case CURSOR_DEFAULT_ON:
            default:
                /* Blinking filled block */
                if (p->cursor_blink_count == DEFAULT_CURSORONTIME) {
                        /* Blink counter says we draw the cursor now */
                        /* Draw a box of foreground color */
                        sprintf(p->comm_buf, "15 %d %d", x_px, y_px); // xy
                        ez_command(drvthis);
                        sprintf(p->comm_buf, "6 %d", p->foreground); // color
                        ez_command(drvthis);
                        sprintf(p->comm_buf, "19 %d %d 1", p->char_width_px,
                                p->char_height_px); // box
                        ez_command(drvthis);
                        break;
                }
                else if(p->cursor_blink_count == DEFAULT_CURSOROFFTIME) {
                        /* Blink counter says we need to hide the cursor
                         * by forcing an update and drawing */
                        prev->type = EZ_DIRTY;
                        ez_draw_at(drvthis, x, y);                
                }
        }
        /* Update counter for the blinking cursors */
        if (p->cursor_blink_count == DEFAULT_CURSOROFFTIME)
                p->cursor_blink_count = 0;
        else
                ++p->cursor_blink_count;
}


/**
 * Draw a vertical bar bottom-up.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column) of the starting point.
 * \param y        Vertical character position (row) of the starting point.
 * \param len      Number of characters that the bar is high at 100%
 * \param promille Current height level of the bar in promille.
 * \param options  Options (currently unused).
 */
MODULE_EXPORT void
ezlcd3xx_vbar(Driver *drvthis, int x, int y, int len, int promille, int options)
{
        PrivateData *p = (PrivateData *) drvthis->private_data;       
        --x; --y;

        if (x < 0 || x >= p->width)
                return;
        if (y < 0 || y >= p->height)
                return;
        if (len < 0)
                return;

        int prom_len = len * promille;  // Promille scaled to length of the bar
        int prom_div = prom_len / 1000; // How many elements are fully filled?
        int prom_rem = prom_len % 1000; // Promille of 0-1000 for the last
                                        // element filled
        int i = 0, data;
        while (y - i > 0 && i < len) {
                if (i < prom_div)
                        data = 1000;
                else if (i == prom_div && prom_rem > 0)
                        data = prom_rem;
                else // Do not set vbar when setting an empty square
                        break;
                ez_element_set(drvthis, x, y - i, EZ_VBAR, data);
                ++i;
        }
}


/**
 * Draw a horizontal bar to the right.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column) of the starting point.
 * \param y        Vertical character position (row) of the starting point.
 * \param len      Number of characters that the bar is long at 100%
 * \param promille Current length level of the bar in promille.
 * \param options  Options (currently unused).
 */
MODULE_EXPORT void
ezlcd3xx_hbar(Driver *drvthis, int x, int y, int len, int promille, int options)
{
        PrivateData *p = (PrivateData *) drvthis->private_data;       

        --x; --y;

        if (x < 0 || x >= p->width)
                return;
        if (y < 0 || y >= p->height)
                return;
        if (len < 0)
                return;

        int prom_len = len * promille;  // Promille scaled to length of the bar
        int prom_div = prom_len / 1000; // How many elements are fully filled?
        int prom_rem = prom_len % 1000; // Promille of 0-1000 for the last
                                        // element filled.
        int i = 0, data;
        while (x + i < p->width && i < len) {
                if (i < prom_div)
                        data = 1000;
                else if (i == prom_div && prom_rem > 0)
                        data = prom_rem;
                else // Do not set hbar when setting an empty square
                        break;
                ez_element_set(drvthis, x + i, y, EZ_HBAR, data);
                ++i;
        }
}


/**
 * Place an icon on the screen.
 * \param drvthis  Pointer to driver structure.
 * \param x        Horizontal character position (column).
 * \param y        Vertical character position (row).
 * \param icon     synbolic value representing the icon.
 * \retval 0       Icon has been successfully defined/written.
 * \retval <0      Server core shall define/write the icon.
 */
MODULE_EXPORT
int ezlcd3xx_icon(Driver *drvthis, int x, int y, int icon)
{
        PrivateData *p = (PrivateData *) drvthis->private_data;

        --x; --y;
        if (x < 0 || x >= p->width)
                return 0;
        if (y < 0 || y >= p->width)
                return 0;

        switch(icon) {
        case ICON_HEART_FILLED:
                if (p->disable_heart)
                        return -1;
                ez_element_set(drvthis, x, y, EZ_ICON, ICON_HEART_FILLED);
                return 0;
        case ICON_HEART_OPEN:
                if (p->disable_heart)
                        return -1;
                ez_element_set(drvthis, x, y, EZ_ICON, ICON_HEART_OPEN);
                return 0;
        case ICON_BLOCK_FILLED:
                ez_element_set(drvthis, x, y, EZ_ICON, ICON_BLOCK_FILLED);
                return 0;
        case ICON_ELLIPSIS:
                ez_element_set(drvthis, x, y, EZ_ICON, ICON_ELLIPSIS);
                return 0;
        default:
                return -1;
        }
}


/**
 * Get key from the device.
 * \param drvthis  Pointer to driver structure.
 * \return         String representation of the key;
 *                 \c NULL if nothing available / unmapped key.
 */
MODULE_EXPORT const char *
ezlcd3xx_get_key(Driver *drvthis)
{
        PrivateData *p = (PrivateData *) drvthis->private_data;
        const char *ret = NULL;
        if (p->up_pressed) {
                ret = "Up";
                p->up_pressed = false;
        }
        else if (p->left_pressed) {
                ret = "Left";
                p->left_pressed = false;
        }
        else if (p->right_pressed) {
                ret = "Right";
                p->right_pressed = false;
        }
        else if (p->down_pressed) {
                ret = "Down";
                p->down_pressed = false;
        }
        else if (p->esc_pressed) {
                ret = "Escape";
                p->esc_pressed = false;
        }
        else if (p->enter_pressed) {
                ret = "Enter";
                p->enter_pressed = false;
        }
        return ret;
}
